from typing import Literal
import pandas as pd
import pandas.api.types as pdt
from pathlib import Path

from ..pipeline.pipeline import PipeModule, Port, status_manage
from ..pipeline.portTypes import PortTypeHint, PortType
from ..pipeline.pipeData import RangeModel
from ..connectors.gdiRequests import *
from ..dataclass.tables import TableData, TableCollection, FieldMetadata
from ..dataclass.results import SingleResult, UnitResult
from ..dataclass.terminologies import Units


@status_manage
class TableDescribe(PipeModule):
    """Apply pandas DataFrame describe() method to TableData and provide described table and selected statistics."""

    def __init__(self, mname: str | None = None, auto_run: bool = True,
                 table: PortTypeHint.TableData | None = None,
                 selected_pairs: dict[str, list[Literal["count", "mean", "std", "min", "25%", "50%", "75%", "max"]]] | None = None,
                 describe_columns: list[str] | None = None) -> None:
        """Initialize the TableDescribe object.
        
        Parameters
        ----------
        table : PortTypeHint.TableData | None, default: None
            The table to describe.

        selected_pairs : dict[str, list[str]] | None, default: None
            Dictionary mapping column names to lists of statistics to select for SingleResult output.
            Key is column name, value is list of statistic names.
            Example: {"columnA": ["count", "mean"], "columnB": ["min", "max", "std"]}
            Available statistics: ['count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max']

        describe_columns : list[str] | None, default: None
            The columns to include in the describe operation for the full table output. 
            If None, all numeric columns will be used.
        """
        super().__init__(mname=mname, auto_run=auto_run)

        self._ports_in = {"InputTable": Port(ptype=PortType.TableData, data=table, pdoc="The table to describe.")}
        self._ports_out = {
            "OutputStatTable": Port(ptype=PortType.TableData, pdoc="The described table with statistical summaries."),
            "OutputSingleResult": Port(ptype=PortType.SingleResult, pdoc="The selected column-statistic pairs as a SingleResult.")
        }

        self.selected_pairs = selected_pairs or {}
        self.describe_columns = describe_columns

    @property
    def InputTable(self) -> None:
        raise AttributeError("Property 'InputTable' is write-only.")
    
    @InputTable.setter
    def InputTable(self, value: PortTypeHint.TableData | None) -> None:
        self._ports_in["InputTable"].data = value

    @property
    def OutputStatTable(self) -> PortTypeHint.TableData | None:
        return self._ports_out["OutputStatTable"].data

    @property
    def OutputSingleResult(self) -> PortTypeHint.SingleResult | None:
        return self._ports_out["OutputSingleResult"].data
    
    def set_cal_params(self, reset: bool = True) -> None:
        return None

    def execute(self) -> PortTypeHint.TableData | None:
        table: PortTypeHint.TableData | None = self._ports_in["InputTable"].data
        if table is None:
            self._ports_out["OutputStatTable"].data = None
            self._ports_out["OutputSingleResult"].data = None
            return None
        
        # Select columns to describe for the full table output
        if self.describe_columns:
            # Convert field titles to column names if necessary
            available_cols = []
            for col in self.describe_columns:
                # Check if it's a column name or field title
                if col in table.columns:
                    # It's a column name
                    available_cols.append(col)
                elif col in table.title_to_name:
                    # It's a field title, get the actual column name
                    actual_col_name = table.title_to_name[col]
                    available_cols.append(actual_col_name)
            
            if available_cols:
                # Use column names to get a TableData subset
                data_to_describe = table[available_cols]
            else:
                # No valid columns found, return None
                self._ports_out["OutputStatTable"].data = None
                self._ports_out["OutputSingleResult"].data = None
                return None
        else:
            # Use all columns - pandas describe() will automatically filter to numeric ones
            data_to_describe = table
        
        # Call describe method (now returns TableData with preserved metadata)
        described_table = data_to_describe.describe()
        
        # Check if we got any numeric columns
        if described_table.empty:
            # No numeric columns found, return None
            self._ports_out["OutputStatTable"].data = None
            self._ports_out["OutputSingleResult"].data = None
            return None
        
        # Collect specific column-statistic pairs for SingleResult
        single_result_list = []
        if self.selected_pairs:
            for column, statistics_list in self.selected_pairs.items():
                # Convert field title to column name if necessary
                actual_column = column
                if column not in table.columns and column in table.title_to_name:
                    actual_column = table.title_to_name[column]
                
                # Process each statistic for this column
                for statistic in statistics_list:
                    # Validate that the column exists and the statistic is available
                    if (actual_column and statistic and 
                        actual_column in described_table.columns and 
                        statistic in described_table.index):
                        
                        value = described_table.loc[statistic, actual_column]
                        # Get original metadata for units
                        original_meta = table.get_field_metadata(actual_column) if actual_column in table.columns else None
                        unit = original_meta.unit if original_meta else Units.UNITLESS
                        
                        unit_result = UnitResult(
                            name=f"{actual_column}_{statistic}",
                            title=f"{original_meta.title if original_meta else actual_column}-{statistic}",
                            unit=unit,
                            value=float(value) if pd.notna(value) else None,
                            description=f"{statistic} of {original_meta.title if original_meta else actual_column} from {table.title or table.name}"
                        )
                        single_result_list.append(unit_result)
        
        # Create SingleResult if we have collected statistics
        single_result = SingleResult(single_result_list) if single_result_list else None
        
        self._ports_out["OutputStatTable"].data = described_table
        self._ports_out["OutputSingleResult"].data = single_result
        return described_table


######################
# Legacy modules
######################

@status_manage
class GdimFieldProportion(PipeModule):
    """Calculate the proportion of a certain type of borehole in all boreholes. """

    def __init__(self, mname: str | None = None, auto_run: bool = False, gbim_http: str | None = None,
                 projNo: str | None = None, pid: int | None = None, tempInfo: PortTypeHint.GdimTemplate | None = None) -> None:
        super().__init__(mname=mname, auto_run=auto_run)
        self._ports_in = {
            "InputTemplateInfo": Port(ptype=PortType.GdimTemplate, data=tempInfo, pdoc=" ")}
        self._ports_out = {"OutputProportion": Port(ptype=PortType.SingleValue, pdoc=" ")}
        self.projNo:str = projNo
        self.pid:int = pid
        self.gbim_http = gbim_http
        self.table_select:str = None
        self.field_select: list = None
        self.cal_item:str = None
        # self._dataf = DataFetch(self.gbim_http)
        # self._proj_info = self._dataf.get_Proj_info()

    def set_cal_params(self, reset: bool = True) -> None:
        values_range = {"table_select": None,"field_select": None,"cal_item": None}
        return values_range

        # output = self._ports_in["InputTemplateInfo"].data
        #
        # if output is not None:
        #     tables_available = list(output.keys())
        #     if reset or self.table_select is None:
        #         self.table_select = tables_available[0]
        #         self.field_select = [output[self.table_select][0]]
        #         self.cal_item = None
        #
        #     values_range["table_select"] = tables_available #选择表单
        #     values_range["field_select"] = output #选择字段
        #
        #     # field_data = self._dataf.get_fields_data_by_params(self.projNo,self.table_select,self.field_select)
        #     # unique_values_count = field_data.nunique()
        #     # values_range["cal_item"] = unique_values_count #选择要计算的统计项名称，比如"原位孔"
        #     values_range["cal_item"] = None #选择要计算的统计项名称，比如"原位孔"
        #
        #
        # return values_range

    def execute(self) -> PortTypeHint.SingleValue | None:
        tempInfo: PortTypeHint.GdimTemplate | None = self._ports_in["InputTemplateInfo"].data
        if tempInfo is None:
            self._ports_out["OutputProportion"].data = None
            return None
        dataf = DataFetch(self.gbim_http)
        field_data = dataf.get_fields_data_by_params(pid=self.pid, projectNo=self.projNo, table_name=self.table_select, field_name_list=self.field_select)
        print(type(field_data))
        count_cal = (field_data == self.cal_item).sum()
        count_all = field_data.count()
        proportion = count_cal / count_all

        self._ports_out["OutputProportion"].data = proportion

        return proportion


@status_manage
class GdimCaveOccurProp(PipeModule):
    """Calculate the proportion of karst cave boreholes """

    def __init__(self,mname: str | None = None,auto_run: bool = False,gbim_http: str | None = None,
                 projNo: str | None = None,pid: int | None = None,tempInfo: PortTypeHint.GdimTemplate | None = None
                 ) -> None:
        super().__init__(mname=mname,auto_run=auto_run)
        self._ports_in = {
            "InputTemplateInfo": Port(ptype=PortType.GdimTemplate,data=tempInfo,pdoc=" ")}
        self._ports_out = {"OutputProportion": Port(ptype=PortType.SingleValue,pdoc=" ")}
        self.projNo:str = projNo
        self.pid:int = pid
        self.gbim_http = gbim_http
        self.table_select:str = None
        self.field_select: list = None
        self.cal_item :str = None
        self.field_bore_num :list = None
        # self._dataf = DataFetch(self.gbim_http)
        # self._proj_info = self._dataf.get_Proj_info()

    def set_cal_params(self,reset: bool = True) -> None:
        values_range = {"table_select": None,"field_select": None,"cal_item": None,"field_bore_num": None}
        return values_range
        # 结构变更，暂时不提供了计算范围了
        # output = self._ports_in["InputTemplateInfo"].data
        #
        # if output is not None:
        #     tables_available = list(output.keys())
        #     if reset or self.table_select is None:
        #         self.table_select = tables_available[0]
        #         self.field_select = [output[self.table_select][0]]
        #         self.cal_item = None
        #
        #     values_range["table_select"] = tables_available  # 选择表单
        #     values_range["field_select"] = output  # 选择需要筛选特定值的字段，比如岩性
        #     values_range["field_bore_num"] = output  # 选择需要统计的字段，这里是钻孔编号，需要统计特定钻孔编号的占比
        #
        #     # field_data = self._dataf.get_fields_data_by_params(self.projNo,self.table_select,self.field_select)
        #     # unique_values_count = field_data.nunique()
        #     # values_range["cal_item"] = unique_values_count  # 选择要计算的统计项名称，比如地层岩性中的"溶洞"
        #     values_range["cal_item"] = None  # 选择要计算的统计项名称，比如地层岩性中的"溶洞"
        #
        # return values_range

    def execute(self) -> PortTypeHint.SingleValue | None:
        tempInfo: PortTypeHint.GdimTemplate | None = self._ports_in["InputTemplateInfo"].data
        if tempInfo is None:
            self._ports_out["OutputProportion"].data = None
            return None
        dataf = DataFetch(self.gbim_http)
        field_data = dataf.get_fields_data_by_params(pid=self.pid, projectNo=self.projNo,table_name=self.table_select,field_name_list=(self.field_bore_num+self.field_select)) #dataframe类型
        bore_num_all = field_data.iloc[:, 0] #地层表中钻孔编号的列数据
        data_filtered = field_data[field_data.iloc[:, 1] == self.cal_item] #筛选岩性为溶洞的部分
        bore_num_select = data_filtered.iloc[:, 0] #地层表中值为溶洞的钻孔编号列数据

        bore_count_all = bore_num_all.nunique()
        bore_count_cal = bore_num_select.nunique()

        proportion = bore_count_cal / bore_count_all

        self._ports_out["OutputProportion"].data = proportion

        return proportion


@status_manage
class GdimCaveLineProp(PipeModule):
    """Calculate the line proportion of  cave boreholes """

    def __init__(self,mname: str | None = None,auto_run: bool = False,gbim_http: str | None = None,
                 projNo: str | None = None,pid: int | None = None,tempInfo: PortTypeHint.GdimTemplate | None = None
                 ) -> None:
        super().__init__(mname=mname,auto_run=auto_run)
        self._ports_in = {
            "InputTemplateInfo": Port(ptype=PortType.GdimTemplate,data=tempInfo,pdoc=" ")}
        self._ports_out = {"OutputProportion": Port(ptype=PortType.SingleValue,pdoc=" ")}
        self.projNo:str = projNo
        self.pid:int = pid
        self.gbim_http = gbim_http
        self.table_select:str = None
        self.field_select: list = None
        self.cal_item: str = None
        self.field_cal: list = None
        # self._dataf = DataFetch(self.gbim_http)
        # self._proj_info = dataf.get_Proj_info()

    def set_cal_params(self,reset: bool = True) -> None:
        values_range = {"table_select": None,"field_select": None,"cal_item": None,"field_cal": None}
        return values_range

        # output = self._ports_in["InputTemplateInfo"].data
        #
        # if output is not None:
        #     tables_available = list(output.keys())
        #     if reset or self.table_select is None:
        #         self.table_select = tables_available[0]
        #         self.field_select = [output[self.table_select][0]]
        #         self.cal_item = None
        #
        #     values_range["table_select"] = tables_available  # 选择表单
        #     values_range["field_select"] = output  # 选择需要筛选特定值的字段，比如岩性
        #     values_range["field_cal"] = output  # 选择需要统计字段，比如厚度
        #
        #     # field_data = self._dataf.get_fields_data_by_params(self.projNo,self.table_select,self.field_select)
        #     # unique_values_count = field_data.nunique()
        #     # values_range["cal_item"] = unique_values_count  # 选择要计算的统计项名称，比如地层岩性中的"溶洞"
        #     values_range["cal_item"] = None  # 选择要计算的统计项名称，比如地层岩性中的"溶洞"
        #
        # return values_range

    def execute(self) -> PortTypeHint.SingleValue | None:
        tempInfo: PortTypeHint.GdimTemplate | None = self._ports_in["InputTemplateInfo"].data
        if tempInfo is None:
            self._ports_out["OutputProportion"].data = None
            return None
        dataf = DataFetch(self.gbim_http)
        field_data = dataf.get_fields_data_by_params(pid=self.pid, projectNo=self.projNo,table_name=self.table_select,
                                                     field_name_list= (self.field_cal + self.field_select))  # dataframe类型
        cal_item_all = field_data.iloc[:,0]  # 地层表中需要计算的列数据，比如厚度
        is_numeric = pdt.is_numeric_dtype(cal_item_all)#判断是否为数字类型
        if not is_numeric:
            print('统计数据中有非数字类型数据')
            return None

        data_filtered = field_data[field_data.iloc[:,1] == self.cal_item]  # 筛选岩性为溶洞的部分
        cal_item_select = data_filtered.iloc[:,0]  # 筛选后待统计或者计算的列数据

        data_all = cal_item_all.sum() #默认忽略缺失值
        data_cal = cal_item_select.sum() #默认忽略缺失值

        if data_all == 0.0:
            print('线岩溶率模块中地层厚度数据为空')
            return None

        proportion = data_cal / data_all

        self._ports_out["OutputProportion"].data = proportion

        return proportion




